import { Asset, AssetManager, EventTarget, assetManager, js } from 'cc';
import { ConstructorOf, Dictionary } from '../../../../wing/assets/src';
import { Manager } from './Manager';
import { LoadItem, LoadItemOneByOne, LoadItems } from '../declarations';

/**
 * - 作者: DoooReyn
 * - 日期: 2023.10.31
 * - 名称: ResLoader
 * - 描述: 资源加载器
 */
export class ResLoader extends Manager {
    /** 事件类型 */
    public static readonly EventType = {
        /** 开始加载 */
        LoadStart: 'load-start',
        /** 加载错误 */
        LoadError: 'load-error',
        /** 努力加载中 */
        LoadProgress: 'load-progress',
        /** 加载完成 */
        LoadEnd: 'load-ended',
    };

    /** 远程资源标记 */
    private static readonly RemoteFlag: 'remote://';

    /** 监听器 */
    private __listeners: Dictionary<EventTarget>;

    /** 获取监听器 */
    public getListener(tag: string) {
        return this.__listeners.fetch(tag) || this.__listeners.add(tag, new EventTarget());
    }

    /** 移除监听器 */
    public removeListener(tag: string) {
        this.__listeners.remove(tag);
    }

    /** 移除所有监听器 */
    public removeAllListener() {
        this.__listeners.clear();
    }

    public get uname() {
        return 'ResLoader';
    }

    protected async onInit(): Promise<void> {
        this.__listeners = new Dictionary();
    }

    protected async onDeinit(): Promise<void> {
        this.removeAllListener();
    }

    /**
     * 加载资源包
     * @param name 资源包名称
     * @returns
     */
    public async loadBundle(name: string): Promise<AssetManager.Bundle | null> {
        return new Promise((resolve) => {
            const ab = assetManager.getBundle(name);
            if (ab) return resolve(ab);

            assetManager.loadBundle(name, (err, ab) => {
                if (err) {
                    this.logger.error(`加载资源包${name}失败`, err);
                }
                return resolve(err ? null : ab);
            });
        });
    }

    /**
     * 加载单个资源
     * @param item 待加载的资源信息
     * @returns
     */
    public async loadOne<T extends Asset>(item: LoadItem<T>): Promise<T | null> {
        return new Promise(async (resolve) => {
            item.bundle ??= 'resources';
            const ab = await this.loadBundle(item.bundle);
            if (!ab) {
                this.logger.error(`没有找到资源包${item.bundle}`);
                return resolve(null);
            }

            ab.load(item.path, item.type, (err, asset: T) => {
                if (err) {
                    this.logger.error(`未找到资源: ${item.bundle}/${item.path}`, err);
                }
                return resolve(err ? null : asset);
            });
        });
    }

    /**
     * 按顺序加载多个资源
     * @param items 按顺序待加载的资源
     * @param tag 加载任务标识（可选）
     * @returns
     */
    public async loadOneByOne(items: LoadItemOneByOne[], tag?: string): Promise<Record<string, Asset>> {
        return new Promise(async (resolve) => {
            this.loadStart(tag);

            const assets: Record<string, Asset> = {};
            const count = items.length;
            let current = 0;
            let asset: Asset | null;
            for (const item of items) {
                ++current;
                if ((asset = await this.loadOne(item))) {
                    assets[asset.uuid] = asset;
                } else {
                    this.loadError(tag, item);
                }
                this.loadProgress(tag, item, asset, current, count);
            }
            this.loadEnd(tag);

            return resolve(assets);
        });
    }

    /**
     * 加载同一类型的资源
     * @param items 待加载的资源列表
     * @param tag 加载任务标识（可选）
     * @returns
     */
    public async loadMany<T extends Asset>(items: LoadItems<T>, tag?: string): Promise<Record<string, T>> {
        return new Promise(async (resolve) => {
            const { bundle, type, paths } = items;
            const itemList = paths.map((path) => {
                return { bundle, type, path };
            });
            const assets = (await this.loadOneByOne(itemList, tag)) as Record<string, T>;
            return resolve(assets);
        });
    }

    /**
     * 加载远程资源
     * @param path 资源路径或网址
     * @param type 资源类型
     * @returns
     */
    public async loadRemote<T extends Asset>(path: string, type: ConstructorOf<T>, ext?: string): Promise<T | null> {
        return new Promise((resolve) => {
            const key = ResLoader.RemoteFlag + path;
            const asset = assetManager.assets.get(key);
            if (asset) return resolve(asset as T);

            assetManager.loadRemote(path, { ext }, (err, asset: T) => {
                if (err) {
                    this.logger.error('加载远程资源失败', err);
                } else if (!(asset instanceof type)) {
                    err = new Error('资源类型不匹配');
                    this.logger.warn('资源类型不匹配', path, js.getClassName(asset));
                } else {
                    assetManager.assets.add(key, asset);
                }
                resolve(err ? null : asset);
            });
        });
    }

    /**
     * 清理远程资源
     * @param path 资源路径或网址
     * @param release 是否需要释放资源，默认释放
     */
    public cleanRemote(path: string, release: boolean = true) {
        const key = ResLoader.RemoteFlag + path;
        const asset = assetManager.assets.get(key);
        if (asset) {
            assetManager.assets.remove(key);
            release && assetManager.releaseAsset(asset);
        }
    }

    /** 加载开始 */
    private loadStart(tag: string | undefined) {
        if (tag) {
            this.getListener(tag).emit(ResLoader.EventType.LoadStart);
        }
    }

    /** 加载错误 */
    private loadError(tag: string | undefined, item: any) {
        if (tag) {
            const target = this.__listeners.fetch(tag);
            if (target) target.emit(ResLoader.EventType.LoadError, item);
        }
    }

    /** 加载中 */
    private loadProgress(tag: string | undefined, item: any, asset: Asset | null, current: number, count: number) {
        if (tag) {
            const target = this.__listeners.fetch(tag);
            if (target) target.emit(ResLoader.EventType.LoadProgress, item, asset, current, count);
        }
    }

    /** 加载完成 */
    private loadEnd(tag: string | undefined) {
        if (tag) {
            const target = this.__listeners.fetch(tag);
            if (target) target.emit(ResLoader.EventType.LoadEnd);
        }
    }
}
